﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.ComponentModel;

namespace Bonobo.Irc.Client
{
    /// <summary>
    /// FIXME: cz! Vezme command a udela z nej byty vezme odpoved a prevede na command
    /// </summary>
    internal class IrcConnection : IDisposable
    {              
        private IPEndPoint _endpoint;
        private TcpClient _client;
        private Stream _stream;
        private volatile IrcConnectionState _state;

        private static readonly Object EventMessageReceived = new Object();
        private static readonly Object EventMessageSent = new Object();
        private static readonly Object EventStateChanged = new Object();

        private readonly List<String> _messages = new List<String>();
        private readonly EventHandlerList _events = new EventHandlerList();



        public event EventHandler StateChanged
        {
            add { _events.AddHandler(EventStateChanged, value); }
            remove { _events.RemoveHandler(EventStateChanged, value); }
        }

        public event EventHandler<IrcMessageEventArgs> MessageSent
        {
            add { _events.AddHandler(EventMessageSent, value); }
            remove { _events.RemoveHandler(EventMessageSent, value); }
        }

        public event EventHandler<IrcMessageEventArgs> MessageReceived
        {
            add { _events.AddHandler(EventMessageReceived, value); }
            remove { _events.RemoveHandler(EventMessageReceived, value); }
        }


    
        public IrcConnection(IPEndPoint endPoint)
        {
            _endpoint = endPoint;
            _client = new TcpClient();
        }



        public IrcConnectionState State
        {
            get { return _state; }
            private set
            {
                if (_state != value)
                {
                    _state = value;
                    _events.InvokeEvent(EventStateChanged, this);
                }
            }
        }


        // FIXME: rename to OpenAsync, make callback optional or remove altogether
        public void BeginOpen()
        {
            State = IrcConnectionState.Opening;
            _client.BeginConnect(_endpoint.Address, _endpoint.Port, OnConnected, null);
        }

        public void Dispose()
        {
            State = IrcConnectionState.Closed;
            if (_stream != null)
            {
                _stream.Dispose();
            }
            _events.Dispose();
        }  

        public void BeginSend(IrcRequest request)
        {
            var bytes = Serialize(request);
            VerifyState(IrcConnectionState.Opened);
            _stream.BeginWrite(bytes, 0, bytes.Length, OnWriteCompleted, request.ToString());
        }
        
        private void OnWriteCompleted(IAsyncResult ar)
        {
            var msg = (String)(ar.AsyncState);
            if (_state == IrcConnectionState.Opened || _state == IrcConnectionState.Closing)
            {
                _stream.EndWrite(ar);
                _messages.Add(msg);

                _events.InvokeEvent(EventMessageSent, this, new IrcMessageEventArgs(msg));

                BeginRead();
            }
        }

        public void BeginRead()
        {
            var buffer = new Byte[512 * 1024];
            if (State != IrcConnectionState.Closed)
            {
                _stream.BeginRead(buffer, 0, buffer.Length, OnReadCompleted, buffer);
            }
        }



        private void OnReadCompleted(IAsyncResult ar)
        {
            var buffer = (Byte[])(ar.AsyncState);
            if (_state == IrcConnectionState.Opened)
            {
                var bytesRead = _stream.EndRead(ar);

                if (bytesRead > 0)
                {
                    // FIXME: define the used encoding in one place
                    var msg = Encoding.UTF8.GetString(buffer, 0, bytesRead);

                    _messages.Add(msg);
                    _events.InvokeEvent(EventMessageReceived, this, new IrcMessageEventArgs(msg));
                }

                if (State == IrcConnectionState.Opened)
                {
                    BeginRead();
                }
            }
        }

        private void OnConnected(IAsyncResult ar)
        {
            try
            {
                _client.EndConnect(ar);
                _stream = _client.GetStream();
                State = IrcConnectionState.Opened;
            }
            catch (SocketException)
            {
                State = IrcConnectionState.Faulted;
                // FIXME: re-throw?!
            }
        }

        private void VerifyState(IrcConnectionState state)
        {
            if (State != state)
            {
                throw new InvalidOperationException();
            }
        }
 


        private static byte[] Serialize(IrcRequest cmd)
        {
            return Encoding.UTF8.GetBytes(cmd.ToString());
        }                
    }








}
